{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configurations for Colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import sys\n",
    "IN_COLAB = 'google.colab' in sys.modules\n",
    "\n",
    "if IN_COLAB:\n",
    "    !apt-get install -y xvfb python-opengl > /dev/null 2>&1\n",
    "    !pip install gym pyvirtualdisplay > /dev/null 2>&1\n",
    "    !pip install JSAnimation==0.1\n",
    "    \n",
    "    from pyvirtualdisplay import Display\n",
    "    \n",
    "    # Start virtual display\n",
    "    dis = Display(visible=0, size=(400, 400))\n",
    "    dis.start()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 05. Noisy Networks for Exploration\n",
    "\n",
    "[M. Fortunato et al., \"Noisy Networks for Exploration.\" arXiv preprint arXiv:1706.10295, 2017.](https://arxiv.org/pdf/1706.10295.pdf)\n",
    "\n",
    "\n",
    "NoisyNet is an exploration method that learns perturbations of the network weights to drive exploration. The key insight is that a single change to the weight vector can induce a consistent, and potentially very complex, state-dependent change in policy over multiple time steps.\n",
    "\n",
    "Firstly, let's take a look into a linear layer of a neural network with $p$ inputs and $q$ outputs, represented by\n",
    "\n",
    "$$\n",
    "y = wx + b,\n",
    "$$\n",
    "\n",
    "where $x \\in \\mathbb{R}^p$ is the layer input, $w \\in \\mathbb{R}^{q \\times p}$, and $b \\in \\mathbb{R}$ the bias.\n",
    "\n",
    "The corresponding noisy linear layer is defined as:\n",
    "\n",
    "$$\n",
    "y = (\\mu^w + \\sigma^w \\odot \\epsilon^w) x + \\mu^b + \\sigma^b \\odot \\epsilon^b,\n",
    "$$\n",
    "\n",
    "where $\\mu^w + \\sigma^w \\odot \\epsilon^w$ and $\\mu^b + \\sigma^b \\odot \\epsilon^b$ replace $w$ and $b$ in the first linear layer equation. The parameters $\\mu^w \\in \\mathbb{R}^{q \\times p}, \\mu^b \\in \\mathbb{R}^q, \\sigma^w \\in \\mathbb{R}^{q \\times p}$ and $\\sigma^b \\in \\mathbb{R}^q$ are learnable, whereas $\\epsilon^w \\in \\mathbb{R}^{q \\times p}$ and $\\epsilon^b \\in \\mathbb{R}^q$ are noise random variables which can be generated by one of the following two ways:\n",
    "\n",
    "1. **Independent Gaussian noise**: the noise applied to each weight and bias is independent, where each random noise entry is drawn from a unit Gaussian distribution. This means that for each noisy linear layer, there are $pq + q$ noise variables (for $p$ inputs to the layer and $q$ outputs).\n",
    "2. **Factorised Gaussian noise:** This is a more computationally efficient way. It produces 2 random Gaussian noise vectors ($p, q$) and makes $pq + q$ noise entries by outer product as follows:\n",
    "\n",
    "$$\n",
    "\\begin{align}\n",
    "\\epsilon_{i,j}^w &= f(\\epsilon_i) f(\\epsilon_j),\\\\\n",
    "\\epsilon_{j}^b &= f(\\epsilon_i),\\\\\n",
    "\\text{where } f(x) &= sgn(x) \\sqrt{|x|}.\n",
    "\\end{align}\n",
    "$$\n",
    "\n",
    "In all experiements of the paper, the authors used Factorised Gaussian noise, so we will go for it as well."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import math\n",
    "import os\n",
    "from typing import Dict, List, Tuple\n",
    "\n",
    "import gym\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "import torch.optim as optim\n",
    "from IPython.display import clear_output\n",
    "from torch.nn.utils import clip_grad_norm_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Replay buffer\n",
    "\n",
    "Please see *01.dqn.ipynb* for detailed description."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class ReplayBuffer:\n",
    "    \"\"\"A simple numpy replay buffer.\"\"\"\n",
    "\n",
    "    def __init__(self, obs_dim: int, size: int, batch_size: int = 32):\n",
    "        self.obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.next_obs_buf = np.zeros([size, obs_dim], dtype=np.float32)\n",
    "        self.acts_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.rews_buf = np.zeros([size], dtype=np.float32)\n",
    "        self.done_buf = np.zeros(size, dtype=np.float32)\n",
    "        self.max_size, self.batch_size = size, batch_size\n",
    "        self.ptr, self.size, = 0, 0\n",
    "\n",
    "    def store(\n",
    "        self,\n",
    "        obs: np.ndarray,\n",
    "        act: np.ndarray, \n",
    "        rew: float, \n",
    "        next_obs: np.ndarray, \n",
    "        done: bool,\n",
    "    ):\n",
    "        self.obs_buf[self.ptr] = obs\n",
    "        self.next_obs_buf[self.ptr] = next_obs\n",
    "        self.acts_buf[self.ptr] = act\n",
    "        self.rews_buf[self.ptr] = rew\n",
    "        self.done_buf[self.ptr] = done\n",
    "        self.ptr = (self.ptr + 1) % self.max_size\n",
    "        self.size = min(self.size + 1, self.max_size)\n",
    "\n",
    "    def sample_batch(self) -> Dict[str, np.ndarray]:\n",
    "        idxs = np.random.choice(self.size, size=self.batch_size, replace=False)\n",
    "        return dict(obs=self.obs_buf[idxs],\n",
    "                    next_obs=self.next_obs_buf[idxs],\n",
    "                    acts=self.acts_buf[idxs],\n",
    "                    rews=self.rews_buf[idxs],\n",
    "                    done=self.done_buf[idxs])\n",
    "\n",
    "    def __len__(self) -> int:\n",
    "        return self.size"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Noisy Layer\n",
    "\n",
    "**References:**\n",
    "- https://github.com/higgsfield/RL-Adventure/blob/master/5.noisy%20dqn.ipynb\n",
    "- https://github.com/Kaixhin/Rainbow/blob/master/model.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "class NoisyLinear(nn.Module):\n",
    "    \"\"\"Noisy linear module for NoisyNet.\n",
    "    \n",
    "    Attributes:\n",
    "        in_features (int): input size of linear module\n",
    "        out_features (int): output size of linear module\n",
    "        std_init (float): initial std value\n",
    "        weight_mu (nn.Parameter): mean value weight parameter\n",
    "        weight_sigma (nn.Parameter): std value weight parameter\n",
    "        bias_mu (nn.Parameter): mean value bias parameter\n",
    "        bias_sigma (nn.Parameter): std value bias parameter\n",
    "        \n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, in_features: int, out_features: int, std_init: float = 0.5):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(NoisyLinear, self).__init__()\n",
    "        \n",
    "        self.in_features = in_features\n",
    "        self.out_features = out_features\n",
    "        self.std_init = std_init\n",
    "\n",
    "        self.weight_mu = nn.Parameter(torch.Tensor(out_features, in_features))\n",
    "        self.weight_sigma = nn.Parameter(\n",
    "            torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "        self.register_buffer(\n",
    "            \"weight_epsilon\", torch.Tensor(out_features, in_features)\n",
    "        )\n",
    "\n",
    "        self.bias_mu = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.bias_sigma = nn.Parameter(torch.Tensor(out_features))\n",
    "        self.register_buffer(\"bias_epsilon\", torch.Tensor(out_features))\n",
    "\n",
    "        self.reset_parameters()\n",
    "        self.reset_noise()\n",
    "\n",
    "    def reset_parameters(self):\n",
    "        \"\"\"Reset trainable network parameters (factorized gaussian noise).\"\"\"\n",
    "        mu_range = 1 / math.sqrt(self.in_features)\n",
    "        self.weight_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.weight_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.in_features)\n",
    "        )\n",
    "        self.bias_mu.data.uniform_(-mu_range, mu_range)\n",
    "        self.bias_sigma.data.fill_(\n",
    "            self.std_init / math.sqrt(self.out_features)\n",
    "        )\n",
    "\n",
    "    def reset_noise(self):\n",
    "        \"\"\"Make new noise.\"\"\"\n",
    "        epsilon_in = self.scale_noise(self.in_features)\n",
    "        epsilon_out = self.scale_noise(self.out_features)\n",
    "\n",
    "        # outer product\n",
    "        self.weight_epsilon.copy_(epsilon_out.ger(epsilon_in))\n",
    "        self.bias_epsilon.copy_(epsilon_out)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\n",
    "        \n",
    "        We don't use separate statements on train / eval mode.\n",
    "        It doesn't show remarkable difference of performance.\n",
    "        \"\"\"\n",
    "        return F.linear(\n",
    "            x,\n",
    "            self.weight_mu + self.weight_sigma * self.weight_epsilon,\n",
    "            self.bias_mu + self.bias_sigma * self.bias_epsilon,\n",
    "        )\n",
    "    \n",
    "    @staticmethod\n",
    "    def scale_noise(size: int) -> torch.Tensor:\n",
    "        \"\"\"Set scale to make noise (factorized gaussian noise).\"\"\"\n",
    "        x = torch.FloatTensor(np.random.normal(loc=0.0, scale=1.0, size=size))\n",
    "\n",
    "        return x.sign().mul(x.abs().sqrt())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Noisy Network\n",
    "\n",
    "We use NoisyLinear for the last two FC layers, and there is a method to reset noise at every step.\n",
    "These are the only differences from the example of *01.dqn.ipynb*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Network(nn.Module):\n",
    "    def __init__(self, in_dim: int, out_dim: int):\n",
    "        \"\"\"Initialization.\"\"\"\n",
    "        super(Network, self).__init__()\n",
    "\n",
    "        self.feature = nn.Linear(in_dim, 128)\n",
    "        self.noisy_layer1 = NoisyLinear(128, 128)\n",
    "        self.noisy_layer2 = NoisyLinear(128, out_dim)\n",
    "\n",
    "    def forward(self, x: torch.Tensor) -> torch.Tensor:\n",
    "        \"\"\"Forward method implementation.\"\"\"\n",
    "        feature = F.relu(self.feature(x))\n",
    "        hidden = F.relu(self.noisy_layer1(feature))\n",
    "        out = self.noisy_layer2(hidden)\n",
    "        \n",
    "        return out\n",
    "    \n",
    "    def reset_noise(self):\n",
    "        \"\"\"Reset all noisy layers.\"\"\"\n",
    "        self.noisy_layer1.reset_noise()\n",
    "        self.noisy_layer2.reset_noise()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## DQN + NoisyNet Agent (w/o DuelingNet)\n",
    "\n",
    "Here is a summary of DQNAgent class.\n",
    "\n",
    "| Method           | Note                                                 |\n",
    "| ---              | ---                                                  |\n",
    "|select_action     | select an action from the input state.               |\n",
    "|step              | take an action and return the response of the env.   |\n",
    "|compute_dqn_loss  | return dqn loss.                                     |\n",
    "|update_model      | update the model by gradient descent.                |\n",
    "|target_hard_update| hard update from the local model to the target model.|\n",
    "|train             | train the agent during num_frames.                   |\n",
    "|test              | test the agent (1 episode).                          |\n",
    "|plot              | plot the training progresses.                        |\n",
    "\n",
    "In the paper, NoisyNet is used as a component of the Dueling Network Architecture, which includes Double-DQN and Prioritized Experience Replay. However, we don't implement them to simplify the tutorial. One thing to note is that NoisyNet is an alternertive to $\\epsilon$-greedy method, so all $\\epsilon$ related lines are removed. Please check all comments with *NoisyNet*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DQNAgent:\n",
    "    \"\"\"DQN Agent interacting with environment.\n",
    "    \n",
    "    Attribute:\n",
    "        env (gym.Env): openAI Gym environment\n",
    "        memory (ReplayBuffer): replay memory to store transitions\n",
    "        batch_size (int): batch size for sampling\n",
    "        target_update (int): period for target model's hard update\n",
    "        gamma (float): discount factor\n",
    "        dqn (Network): model to train and select actions\n",
    "        dqn_target (Network): target model to update\n",
    "        optimizer (torch.optim): optimizer for training dqn\n",
    "        transition (list): transition information including\n",
    "                           state, action, reward, next_state, done\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(\n",
    "        self, \n",
    "        env: gym.Env,\n",
    "        memory_size: int,\n",
    "        batch_size: int,\n",
    "        target_update: int,\n",
    "        gamma: float = 0.99,\n",
    "    ):\n",
    "        \"\"\"Initialization.\n",
    "        \n",
    "        Args:\n",
    "            env (gym.Env): openAI Gym environment\n",
    "            memory_size (int): length of memory\n",
    "            batch_size (int): batch size for sampling\n",
    "            target_update (int): period for target model's hard update\n",
    "            gamma (float): discount factor\n",
    "        \"\"\"\n",
    "        # NoisyNet: All attributes related to epsilon are removed\n",
    "        obs_dim = env.observation_space.shape[0]\n",
    "        action_dim = env.action_space.n\n",
    "        \n",
    "        self.env = env\n",
    "        self.memory = ReplayBuffer(obs_dim, memory_size, batch_size)\n",
    "        self.batch_size = batch_size\n",
    "        self.target_update = target_update\n",
    "        self.gamma = gamma\n",
    "        \n",
    "        # device: cpu / gpu\n",
    "        self.device = torch.device(\n",
    "            \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "        )\n",
    "        print(self.device)\n",
    "\n",
    "        # networks: dqn, dqn_target\n",
    "        self.dqn = Network(obs_dim, action_dim).to(self.device)\n",
    "        self.dqn_target = Network(obs_dim, action_dim).to(self.device)\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "        self.dqn_target.eval()\n",
    "        \n",
    "        # optimizer\n",
    "        self.optimizer = optim.Adam(self.dqn.parameters())\n",
    "\n",
    "        # transition to store in memory\n",
    "        self.transition = list()\n",
    "        \n",
    "        # mode: train / test\n",
    "        self.is_test = False\n",
    "\n",
    "    def select_action(self, state: np.ndarray) -> np.ndarray:\n",
    "        \"\"\"Select an action from the input state.\"\"\"\n",
    "        # NoisyNet: no epsilon greedy action selection\n",
    "        selected_action = self.dqn(\n",
    "            torch.FloatTensor(state).to(self.device)\n",
    "        ).argmax()\n",
    "        selected_action = selected_action.detach().cpu().numpy()\n",
    "        \n",
    "        if not self.is_test:\n",
    "            self.transition = [state, selected_action]\n",
    "        \n",
    "        return selected_action\n",
    "\n",
    "    def step(self, action: np.ndarray) -> Tuple[np.ndarray, np.float64, bool]:\n",
    "        \"\"\"Take an action and return the response of the env.\"\"\"\n",
    "        next_state, reward, done, _ = self.env.step(action)\n",
    "\n",
    "        if not self.is_test:\n",
    "            self.transition += [reward, next_state, done]\n",
    "            self.memory.store(*self.transition)\n",
    "    \n",
    "        return next_state, reward, done\n",
    "\n",
    "    def update_model(self) -> torch.Tensor:\n",
    "        \"\"\"Update the model by gradient descent.\"\"\"\n",
    "        samples = self.memory.sample_batch()\n",
    "\n",
    "        loss = self._compute_dqn_loss(samples)\n",
    "\n",
    "        self.optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        # gradient clipping\n",
    "        # https://pytorch.org/docs/stable/nn.html#torch.nn.utils.clip_grad_norm_\n",
    "        clip_grad_norm_(self.dqn.parameters(), 1.0, norm_type=1)\n",
    "        self.optimizer.step()\n",
    "        \n",
    "        # NoisyNet: reset noise\n",
    "        self.dqn.reset_noise()\n",
    "        self.dqn_target.reset_noise()\n",
    "\n",
    "        return loss.item()\n",
    "        \n",
    "    def train(self, num_frames: int, plotting_interval: int = 200):\n",
    "        \"\"\"Train the agent.\"\"\"\n",
    "        self.is_test = False\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        update_cnt = 0\n",
    "        losses = []\n",
    "        scores = []\n",
    "        score = 0\n",
    "\n",
    "        for frame_idx in range(1, num_frames + 1):\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "            \n",
    "            # NoisyNet: removed decrease of epsilon\n",
    "\n",
    "            # if episode ends\n",
    "            if done:\n",
    "                state = env.reset()\n",
    "                scores.append(score)\n",
    "                score = 0\n",
    "\n",
    "            # if training is ready\n",
    "            if len(self.memory) >= self.batch_size:\n",
    "                loss = self.update_model()\n",
    "                losses.append(loss)\n",
    "                update_cnt += 1\n",
    "                \n",
    "                # if hard update is needed\n",
    "                if update_cnt % self.target_update == 0:\n",
    "                    self._target_hard_update()\n",
    "\n",
    "            # plotting\n",
    "            if frame_idx % plotting_interval == 0:\n",
    "                self._plot(frame_idx, scores, losses)\n",
    "                \n",
    "        self.env.close()\n",
    "                \n",
    "    def test(self) -> List[np.ndarray]:\n",
    "        \"\"\"Test the agent.\"\"\"\n",
    "        self.is_test = True\n",
    "        \n",
    "        state = self.env.reset()\n",
    "        done = False\n",
    "        score = 0\n",
    "        \n",
    "        frames = []\n",
    "        while not done:\n",
    "            frames.append(self.env.render(mode=\"rgb_array\"))\n",
    "            action = self.select_action(state)\n",
    "            next_state, reward, done = self.step(action)\n",
    "\n",
    "            state = next_state\n",
    "            score += reward\n",
    "        \n",
    "        print(\"score: \", score)\n",
    "        self.env.close()\n",
    "        \n",
    "        return frames\n",
    "\n",
    "    def _compute_dqn_loss(self, samples: Dict[str, np.ndarray]) -> torch.Tensor:\n",
    "        \"\"\"Return dqn loss.\"\"\"\n",
    "        device = self.device  # for shortening the following lines\n",
    "        state = torch.FloatTensor(samples[\"obs\"]).to(device)\n",
    "        next_state = torch.FloatTensor(samples[\"next_obs\"]).to(device)\n",
    "        action = torch.LongTensor(samples[\"acts\"].reshape(-1, 1)).to(device)\n",
    "        reward = torch.FloatTensor(samples[\"rews\"].reshape(-1, 1)).to(device)\n",
    "        done = torch.FloatTensor(samples[\"done\"].reshape(-1, 1)).to(device)\n",
    "        \n",
    "        # G_t   = r + gamma * v(s_{t+1})  if state != Terminal\n",
    "        #       = r                       otherwise\n",
    "        curr_q_value = self.dqn(state).gather(1, action)\n",
    "        next_q_value = self.dqn_target(next_state).max(\n",
    "            dim=1, keepdim=True\n",
    "        )[0].detach()\n",
    "        mask = 1 - done\n",
    "        target = (reward + self.gamma * next_q_value * mask).to(self.device)\n",
    "\n",
    "        # calculate dqn loss\n",
    "        loss = ((target - curr_q_value).pow(2)).mean()\n",
    "\n",
    "        return loss\n",
    "\n",
    "    def _target_hard_update(self):\n",
    "        \"\"\"Hard update: target <- local.\"\"\"\n",
    "        self.dqn_target.load_state_dict(self.dqn.state_dict())\n",
    "                \n",
    "    def _plot(\n",
    "        self, \n",
    "        frame_idx: int, \n",
    "        scores: List[float], \n",
    "        losses: List[float], \n",
    "    ):\n",
    "        \"\"\"Plot the training progresses.\"\"\"\n",
    "        clear_output(True)\n",
    "        plt.figure(figsize=(20, 5))\n",
    "        plt.subplot(131)\n",
    "        plt.title('frame %s. score: %s' % (frame_idx, np.mean(scores[-10:])))\n",
    "        plt.plot(scores)\n",
    "        plt.subplot(132)\n",
    "        plt.title('loss')\n",
    "        plt.plot(losses)\n",
    "        plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Environment\n",
    "\n",
    "You can see the [code](https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py) and [configurations](https://github.com/openai/gym/blob/master/gym/envs/__init__.py#L53) of CartPole-v0 from OpenAI's repository."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# environment\n",
    "env_id = \"CartPole-v0\"\n",
    "env = gym.make(env_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Set random seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[777]"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "seed = 777\n",
    "\n",
    "def seed_torch(seed):\n",
    "    torch.manual_seed(seed)\n",
    "    if torch.backends.cudnn.enabled:\n",
    "        torch.backends.cudnn.benchmark = False\n",
    "        torch.backends.cudnn.deterministic = True\n",
    "\n",
    "np.random.seed(seed)\n",
    "seed_torch(seed)\n",
    "env.seed(seed)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Initialize"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda\n"
     ]
    }
   ],
   "source": [
    "# parameters\n",
    "num_frames = 10000\n",
    "memory_size = 1000\n",
    "batch_size = 32\n",
    "target_update = 200\n",
    "\n",
    "# train\n",
    "agent = DQNAgent(env, memory_size, batch_size, target_update)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Train"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 1440x360 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "agent.train(num_frames)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Test\n",
    "\n",
    "Run the trained agent (1 episode)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "score:  116.0\n"
     ]
    }
   ],
   "source": [
    "frames = agent.test()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Render"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "<script language=\"javascript\">\n",
       "  /* Define the Animation class */\n",
       "  function Animation(frames, img_id, slider_id, interval, loop_select_id){\n",
       "    this.img_id = img_id;\n",
       "    this.slider_id = slider_id;\n",
       "    this.loop_select_id = loop_select_id;\n",
       "    this.interval = interval;\n",
       "    this.current_frame = 0;\n",
       "    this.direction = 0;\n",
       "    this.timer = null;\n",
       "    this.frames = new Array(frames.length);\n",
       "\n",
       "    for (var i=0; i<frames.length; i++)\n",
       "    {\n",
       "     this.frames[i] = new Image();\n",
       "     this.frames[i].src = frames[i];\n",
       "    }\n",
       "    document.getElementById(this.slider_id).max = this.frames.length - 1;\n",
       "    this.set_frame(this.current_frame);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.get_loop_state = function(){\n",
       "    var button_group = document[this.loop_select_id].state;\n",
       "    for (var i = 0; i < button_group.length; i++) {\n",
       "        var button = button_group[i];\n",
       "        if (button.checked) {\n",
       "            return button.value;\n",
       "        }\n",
       "    }\n",
       "    return undefined;\n",
       "  }\n",
       "\n",
       "  Animation.prototype.set_frame = function(frame){\n",
       "    this.current_frame = frame;\n",
       "    document.getElementById(this.img_id).src = this.frames[this.current_frame].src;\n",
       "    document.getElementById(this.slider_id).value = this.current_frame;\n",
       "  }\n",
       "\n",
       "  Animation.prototype.next_frame = function()\n",
       "  {\n",
       "    this.set_frame(Math.min(this.frames.length - 1, this.current_frame + 1));\n",
       "  }\n",
       "\n",
       "  Animation.prototype.previous_frame = function()\n",
       "  {\n",
       "    this.set_frame(Math.max(0, this.current_frame - 1));\n",
       "  }\n",
       "\n",
       "  Animation.prototype.first_frame = function()\n",
       "  {\n",
       "    this.set_frame(0);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.last_frame = function()\n",
       "  {\n",
       "    this.set_frame(this.frames.length - 1);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.slower = function()\n",
       "  {\n",
       "    this.interval /= 0.7;\n",
       "    if(this.direction > 0){this.play_animation();}\n",
       "    else if(this.direction < 0){this.reverse_animation();}\n",
       "  }\n",
       "\n",
       "  Animation.prototype.faster = function()\n",
       "  {\n",
       "    this.interval *= 0.7;\n",
       "    if(this.direction > 0){this.play_animation();}\n",
       "    else if(this.direction < 0){this.reverse_animation();}\n",
       "  }\n",
       "\n",
       "  Animation.prototype.anim_step_forward = function()\n",
       "  {\n",
       "    this.current_frame += 1;\n",
       "    if(this.current_frame < this.frames.length){\n",
       "      this.set_frame(this.current_frame);\n",
       "    }else{\n",
       "      var loop_state = this.get_loop_state();\n",
       "      if(loop_state == \"loop\"){\n",
       "        this.first_frame();\n",
       "      }else if(loop_state == \"reflect\"){\n",
       "        this.last_frame();\n",
       "        this.reverse_animation();\n",
       "      }else{\n",
       "        this.pause_animation();\n",
       "        this.last_frame();\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.anim_step_reverse = function()\n",
       "  {\n",
       "    this.current_frame -= 1;\n",
       "    if(this.current_frame >= 0){\n",
       "      this.set_frame(this.current_frame);\n",
       "    }else{\n",
       "      var loop_state = this.get_loop_state();\n",
       "      if(loop_state == \"loop\"){\n",
       "        this.last_frame();\n",
       "      }else if(loop_state == \"reflect\"){\n",
       "        this.first_frame();\n",
       "        this.play_animation();\n",
       "      }else{\n",
       "        this.pause_animation();\n",
       "        this.first_frame();\n",
       "      }\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.pause_animation = function()\n",
       "  {\n",
       "    this.direction = 0;\n",
       "    if (this.timer){\n",
       "      clearInterval(this.timer);\n",
       "      this.timer = null;\n",
       "    }\n",
       "  }\n",
       "\n",
       "  Animation.prototype.play_animation = function()\n",
       "  {\n",
       "    this.pause_animation();\n",
       "    this.direction = 1;\n",
       "    var t = this;\n",
       "    if (!this.timer) this.timer = setInterval(function(){t.anim_step_forward();}, this.interval);\n",
       "  }\n",
       "\n",
       "  Animation.prototype.reverse_animation = function()\n",
       "  {\n",
       "    this.pause_animation();\n",
       "    this.direction = -1;\n",
       "    var t = this;\n",
       "    if (!this.timer) this.timer = setInterval(function(){t.anim_step_reverse();}, this.interval);\n",
       "  }\n",
       "</script>\n",
       "\n",
       "<div class=\"animation\" align=\"center\">\n",
       "    <img id=\"_anim_imgDIJSPYUVAAIVCOHT\">\n",
       "    <br>\n",
       "    <input id=\"_anim_sliderDIJSPYUVAAIVCOHT\" type=\"range\" style=\"width:350px\" name=\"points\" min=\"0\" max=\"1\" step=\"1\" value=\"0\" onchange=\"animDIJSPYUVAAIVCOHT.set_frame(parseInt(this.value));\"></input>\n",
       "    <br>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.slower()\">&#8211;</button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.first_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.previous_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.reverse_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.pause_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.play_animation()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.next_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.last_frame()\"><img class=\"anim_icon\" src=\"\"></button>\n",
       "    <button onclick=\"animDIJSPYUVAAIVCOHT.faster()\">+</button>\n",
       "  <form action=\"#n\" name=\"_anim_loop_selectDIJSPYUVAAIVCOHT\" class=\"anim_control\">\n",
       "    <input type=\"radio\" name=\"state\" value=\"once\" > Once </input>\n",
       "    <input type=\"radio\" name=\"state\" value=\"loop\" checked> Loop </input>\n",
       "    <input type=\"radio\" name=\"state\" value=\"reflect\" > Reflect </input>\n",
       "  </form>\n",
       "</div>\n",
       "\n",
       "\n",
       "<script language=\"javascript\">\n",
       "  /* Instantiate the Animation class. */\n",
       "  /* The IDs given should match those used in the template above. */\n",
       "  (function() {\n",
       "    var img_id = \"_anim_imgDIJSPYUVAAIVCOHT\";\n",
       "    var slider_id = \"_anim_sliderDIJSPYUVAAIVCOHT\";\n",
       "    var loop_select_id = \"_anim_loop_selectDIJSPYUVAAIVCOHT\";\n",
       "    var frames = new Array(0);\n",
       "    \n",
       "  frames[0] = \"\"\n",
       "  frames[1] = \"\"\n",
       "  frames[2] = \"\"\n",
       "  frames[3] = \"\"\n",
       "  frames[4] = \"\"\n",
       "  frames[5] = \"\"\n",
       "  frames[6] = \"\"\n",
       "  frames[7] = \"\"\n",
       "  frames[8] = \"\"\n",
       "  frames[9] = \"\"\n",
       "  frames[10] = \"\"\n",
       "  frames[11] = \"\"\n",
       "  frames[12] = \"\"\n",
       "  frames[13] = \"\"\n",
       "  frames[14] = \"\"\n",
       "  frames[15] = \"\"\n",
       "  frames[16] = \"\"\n",
       "  frames[17] = \"\"\n",
       "  frames[18] = \"\"\n",
       "  frames[19] = \"\"\n",
       "  frames[20] = \"\"\n",
       "  frames[21] = \"\"\n",
       "  frames[22] = \"\"\n",
       "  frames[23] = \"\"\n",
       "  frames[24] = \"\"\n",
       "  frames[25] = \"\"\n",
       "  frames[26] = \"\"\n",
       "  frames[27] = \"\"\n",
       "  frames[28] = \"\"\n",
       "  frames[29] = \"\"\n",
       "  frames[30] = \"\"\n",
       "  frames[31] = \"\"\n",
       "  frames[32] = \"\"\n",
       "  frames[33] = \"\"\n",
       "  frames[34] = \"\"\n",
       "  frames[35] = \"\"\n",
       "  frames[36] = \"\"\n",
       "  frames[37] = \"\"\n",
       "  frames[38] = \"\"\n",
       "  frames[39] = \"\"\n",
       "  frames[40] = \"\"\n",
       "  frames[41] = \"\"\n",
       "  frames[42] = \"\"\n",
       "  frames[43] = \"\"\n",
       "  frames[44] = \"\"\n",
       "  frames[45] = \"\"\n",
       "  frames[46] = \"\"\n",
       "  frames[47] = \"\"\n",
       "  frames[48] = \"\"\n",
       "  frames[49] = \"\"\n",
       "  frames[50] = \"\"\n",
       "  frames[51] = \"\"\n",
       "  frames[52] = \"\"\n",
       "  frames[53] = \"\"\n",
       "  frames[54] = \"\"\n",
       "  frames[55] = \"\"\n",
       "  frames[56] = \"\"\n",
       "  frames[57] = \"\"\n",
       "  frames[58] = \"\"\n",
       "  frames[59] = \"\"\n",
       "  frames[60] = \"\"\n",
       "  frames[61] = \"\"\n",
       "  frames[62] = \"\"\n",
       "  frames[63] = \"\"\n",
       "  frames[64] = \"\"\n",
       "  frames[65] = \"\"\n",
       "  frames[66] = \"\"\n",
       "  frames[67] = \"\"\n",
       "  frames[68] = \"\"\n",
       "  frames[69] = \"\"\n",
       "  frames[70] = \"\"\n",
       "  frames[71] = \"\"\n",
       "  frames[72] = \"\"\n",
       "  frames[73] = \"\"\n",
       "  frames[74] = \"\"\n",
       "  frames[75] = \"\"\n",
       "  frames[76] = \"\"\n",
       "  frames[77] = \"\"\n",
       "  frames[78] = \"\"\n",
       "  frames[79] = \"\"\n",
       "  frames[80] = \"\"\n",
       "  frames[81] = \"\"\n",
       "  frames[82] = \"\"\n",
       "  frames[83] = \"\"\n",
       "  frames[84] = \"\"\n",
       "  frames[85] = \"\"\n",
       "  frames[86] = \"\"\n",
       "  frames[87] = \"\"\n",
       "  frames[88] = \"\"\n",
       "  frames[89] = \"\"\n",
       "  frames[90] = \"\"\n",
       "  frames[91] = \"\"\n",
       "  frames[92] = \"\"\n",
       "  frames[93] = \"\"\n",
       "  frames[94] = \"\"\n",
       "  frames[95] = \"\"\n",
       "  frames[96] = \"\"\n",
       "  frames[97] = \"\"\n",
       "  frames[98] = \"\"\n",
       "  frames[99] = \"\"\n",
       "  frames[100] = \"\"\n",
       "  frames[101] = \"\"\n",
       "  frames[102] = \"\"\n",
       "  frames[103] = \"\"\n",
       "  frames[104] = \"\"\n",
       "  frames[105] = \"\"\n",
       "  frames[106] = \"\"\n",
       "  frames[107] = \"\"\n",
       "  frames[108] = \"\"\n",
       "  frames[109] = \"\"\n",
       "  frames[110] = \"\"\n",
       "  frames[111] = \"\"\n",
       "  frames[112] = \"\"\n",
       "  frames[113] = \"\"\n",
       "  frames[114] = \"\"\n",
       "  frames[115] = \"\"\n",
       "\n",
       "\n",
       "    /* set a timeout to make sure all the above elements are created before\n",
       "       the object is initialized. */\n",
       "    setTimeout(function() {\n",
       "        animDIJSPYUVAAIVCOHT = new Animation(frames, img_id, slider_id, 50, loop_select_id);\n",
       "    }, 0);\n",
       "  })()\n",
       "</script>\n"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Imports specifically so we can render outputs in Colab.\n",
    "from matplotlib import animation\n",
    "from JSAnimation.IPython_display import display_animation\n",
    "from IPython.display import display\n",
    "\n",
    "\n",
    "def display_frames_as_gif(frames):\n",
    "    \"\"\"Displays a list of frames as a gif, with controls.\"\"\"\n",
    "    patch = plt.imshow(frames[0])\n",
    "    plt.axis('off')\n",
    "\n",
    "    def animate(i):\n",
    "        patch.set_data(frames[i])\n",
    "\n",
    "    anim = animation.FuncAnimation(\n",
    "        plt.gcf(), animate, frames = len(frames), interval=50\n",
    "    )\n",
    "    display(display_animation(anim, default_mode='loop'))\n",
    "    \n",
    "        \n",
    "# display \n",
    "display_frames_as_gif(frames)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
